package br.com.munif.util;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;

public class GeraDiagramaClasses {

    private List<String> associacoes = new ArrayList<String>();
    private FileWriter fileWriter;
    private List<Class> enums;
    private Map<String, List<Class>> grupos;
    private static List<String> lista;

    public GeraDiagramaClasses(File pastaEntidades, String grupoMostrar) {
        try {
            grupos = new HashMap<String, List<Class>>();
            grupos.put("demais", new ArrayList<Class>());
            enums = new ArrayList<Class>();
            //fileWriter = new FileWriter("diagramas/classes.dot", false);
            fileWriter = new FileWriter("diagramas/" + grupoMostrar + ".dot", false);

            escreveCabecalho(fileWriter);

            for (File arquivo : pastaEntidades.listFiles()) {
                adicionaClasse(arquivo);
            }
            for (String grupo : grupos.keySet()) {
                System.out.println("Grupo " + grupo);
                if (!grupo.equals("demais") && lista.contains(grupo)) {
                    fileWriter.write("subgraph cluster" + grupo + "\n{\n");
                }
                if (grupo.equals(grupoMostrar)) {
                    for (Class entidade : grupos.get(grupo)) {
                        criaClasse(entidade, fileWriter);
                        verificaHeranca(entidade);
                    }
                }
                if (!grupo.equals("demais") && lista.contains(grupo)) {
                    fileWriter.write("}\n");
                }

            }
            for (String a : associacoes) {
                fileWriter.write(a + "\n");
            }
            fileWriter.write("}\n");
            fileWriter.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void escreveCabecalho(FileWriter fileWriter) throws IOException {
        fileWriter.write("digraph G{\n"
                + "fontname = \"Bitstream Vera Sans\"\n"
                + "fontsize = 8\n\n"
                + "node [\n"
                + "        fontname = \"Bitstream Vera Sans\"\n"
                + "        fontsize = 8\n"
                + "        shape = \"record\"\n"
                + "]\n\n"
                + "edge [\n"
                + "        fontname = \"Bitstream Vera Sans\"\n"
                + "        fontsize = 8\n"
                + "]\n\n");
    }

    private void adicionaClasse(File arquivo) throws ClassNotFoundException {
        System.out.println(arquivo);
        if (arquivo.isDirectory() && !arquivo.getName().equals(".")) {
            for (File f : arquivo.listFiles()) {
                System.out.println("--->" + f);
                adicionaClasse(arquivo);
            }
            return;
        }
        String nomeArquivoSimples = arquivo.getAbsolutePath();
        nomeArquivoSimples = nomeArquivoSimples.replaceAll("\\\\", "/");
        int posicao = nomeArquivoSimples.indexOf("/" + "br");
        nomeArquivoSimples = nomeArquivoSimples.substring(posicao + 1);
        nomeArquivoSimples = nomeArquivoSimples.replace("/" + "", ".");
        nomeArquivoSimples = nomeArquivoSimples.replace(".class", "");
        Class entidade = Class.forName(nomeArquivoSimples);
        if (entidade.isEnum()) {
            enums.add(entidade);
        } else {
            if (entidade.isAnnotationPresent(GrupoDiagrama.class)) {
                GrupoDiagrama gd = (GrupoDiagrama) entidade.getAnnotation(GrupoDiagrama.class);
                String nome = gd.nome();
                List<Class> lista = grupos.get(nome);
                if (lista == null) {
                    System.out.println("Novo Grupo " + nome);
                    lista = new ArrayList<Class>();
                    grupos.put(nome, lista);
                }
                lista.add(entidade);
            } else {
                grupos.get("demais").add(entidade);
            }
        }
    }

    private void verificaHeranca(Class entidade) {
        if (entidade.getSuperclass().getCanonicalName().contains("entidades")) {
            associacoes.add("edge [ arrowhead = \"empty\" headlabel = \"\" taillabel = \"\"] " + entidade.getSimpleName() + " -> " + entidade.getSuperclass().getSimpleName());
        }
    }

    private void criaClasse(Class entidade, FileWriter fw) throws Exception {
        String cor = "";
        if (entidade.isAnnotationPresent(CorEntidade.class)) {
            CorEntidade ce = (CorEntidade) entidade.getAnnotation(CorEntidade.class);
            cor = "style=bold,color=\"" + ce.value() + "\"";
        }
        fw.write(entidade.getSimpleName() + " [" + cor + "label = \"{" + entidade.getSimpleName() + "|");
        Field atributos[] = entidade.getDeclaredFields();
        System.out.println(entidade.getSimpleName());
        int i = 0;
        for (Field f : atributos) {
            i++;
            Class tipoAtributo = f.getType();
            String nomeCompleto = tipoAtributo.getCanonicalName();
            String tipo = tipoAtributo.getSimpleName();
            String nomeAtributo = f.getName();
            if ((f.getModifiers() & Modifier.STATIC) != 0) {
                continue;
            }
            if (f.getType().equals(List.class) || f.getType().equals(Set.class) || f.getType().equals(Map.class)) {
                ParameterizedType type = (ParameterizedType) f.getGenericType();
                Type[] typeArguments = type.getActualTypeArguments();
                Class tipoGenerico = (Class) typeArguments[f.getType().equals(Map.class) ? 1 : 0];
                if (f.isAnnotationPresent(ManyToMany.class)) {
                    associacoes.add("edge [arrowhead = \"none\" headlabel = \"*\" taillabel = \"*\"] " + entidade.getSimpleName() + " -> " + tipoGenerico.getSimpleName() + " [label = \"" + nomeAtributo + "\"]");
                } else if (f.isAnnotationPresent(OneToMany.class)) {
                    OneToMany oo = f.getAnnotation(OneToMany.class);
                    if (oo.mappedBy().length() == 0) {
                        associacoes.add("edge [arrowhead = \"none\" headlabel = \"*\" taillabel = \"1\"] " + entidade.getSimpleName() + " -> " + tipoGenerico.getSimpleName() + " [label = \"" + nomeAtributo + "\"]");
                    }
                }

            } else if (nomeCompleto.contains("entidades")) {
                if (f.isAnnotationPresent(ManyToOne.class)) {
                    associacoes.add("edge [arrowhead = \"none\" headlabel = \"1\" taillabel = \"*\"] " + entidade.getSimpleName() + " -> " + tipo + " [label = \"" + nomeAtributo + "\"]");
                } else if (f.isAnnotationPresent(OneToOne.class)) {
                    OneToOne oo = f.getAnnotation(OneToOne.class);
                    if (oo.mappedBy().length() == 0) {
                        associacoes.add("edge [arrowhead = \"none\" headlabel = \"1\" taillabel = \"1\"] " + entidade.getSimpleName() + " -> " + tipo + " [label = \"" + nomeAtributo + "\"]");
                    }

                } else {
                    // fw.write(nomeAtributo + ":" + tipo + "\\l");
                }

            } else {
                // name  : string\l+ age : int\l
                //fw.write(nomeAtributo + ":" + tipo + "\\l");
            }
            if (f.getType().equals(List.class) || f.getType().equals(Set.class) || f.getType().equals(Map.class)) {
                ParameterizedType type = (ParameterizedType) f.getGenericType();
                Type[] typeArguments = type.getActualTypeArguments();
                Class tipoGenerico = (Class) typeArguments[f.getType().equals(Map.class) ? 1 : 0];
                fw.write(nomeAtributo + ":" + tipo + " " + tipoGenerico.getSimpleName() + "\\l");

            } else {
                fw.write(nomeAtributo + ":" + tipo + "\\l");
            }
        }
        fw.write("|\\l}\"]\n");
    }

    public static void main(String args[]) {
        criaListaDeGrupos();
        String pacoteEntidades = "br.com.automenu.web.entidades";
        for (String grupo : lista) {
            File pastaAtual = new File(GeraDiagramaClasses.class.getResource("GeraDiagramaClasses.class").getFile());
            String pastaRaizDasClasses = pastaAtual.toString().substring(0, pastaAtual.toString().indexOf("WEB-INF"+File.separator+"classes") + 15);
            pastaRaizDasClasses=pastaRaizDasClasses.replace("%20", " ");
            String pastaRaizDasEntidades = pastaRaizDasClasses + "/" + pacoteEntidades.replaceAll("\\.", "/");
            System.out.println("Pasta raiz das entidades:" + pastaRaizDasEntidades);
            File pastaEntidades = new File(pastaRaizDasEntidades);
            if (pastaEntidades.exists()) {
                GeraDiagramaClasses gdc = new GeraDiagramaClasses(pastaEntidades, grupo);
            } else {
                System.out.println("Pasta não encontrada " + pastaEntidades);
            }
        }
    }

    public static void criaListaDeGrupos() {
        lista = new ArrayList<String>();
        lista.add("atendimento");
        lista.add("menu");
        lista.add("util");
    }
}



